﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Soundelle.SoundPlayer
{
    public class WavFileCreator
    {
        private struct WavHeader
        {
            // RIFF header = 12 bytes
            public const byte RIFF1 = (byte)'R';
            public const byte RIFF2 = (byte)'I';
            public const byte RIFF3 = (byte)'F';
            public const byte RIFF4 = (byte)'F';
            public uint Size; // filesize - 8 or sizeOfData + 44 - 8 
            public const byte WAVEfmt1 = (byte)'W';
            public const byte WAVEfmt2 = (byte)'A';
            public const byte WAVEfmt3 = (byte)'V';
            public const byte WAVEfmt4 = (byte)'E';

            // fmt chunk = 24 bytes
            public const byte WAVEfmt5 = (byte)'f';
            public const byte WAVEfmt6 = (byte)'m';
            public const byte WAVEfmt7 = (byte)'t';
            public const byte WAVEfmt8 = (byte)' ';
            //
            public const UInt32 SizeOfFmtChunk = 0x000010; // リニアPCM
            public const UInt16 FormatID = 1;
            public UInt16 NumOfChannel; // mono
            public UInt32 SamplingRate; // 48kHz
            public UInt32 DataSpeed; // 48kHz 16bit mono　48000 x 2 x 1 = 96000 (byte/sec)。
            public UInt16 BlockSize; // 16 bit mono 2 x 1(byte/sample)。
            public UInt16 BitOfSample; // 16bit

            // data chunk = 8 + data bytes
            public const byte data1 = (byte)'d';
            public const byte data2 = (byte)'a';
            public const byte data3 = (byte)'t';
            public const byte data4 = (byte)'a';
            public UInt32 SizeOfData; // データのサイズ
            // これ以降、データ部分
        }

        public static bool WriteWav16(ref double[] Data, string Filename, long SamplingRate, double NormalizeRate)
        {
            bool result = true;

            if (NormalizeRate == 0.0)
            {
                double max = 0.0;
                for (long i = 0; i < Data.Length; ++i)
                {
                    if (max < Math.Abs(Data[i]))
                        max = Math.Abs(Data[i]);
                }

                if (max <= 0.0)
                    max = 1.0;

                NormalizeRate = 30000.0 / max;
            }

            if (SamplingRate == 0)
                SamplingRate = 48000;

            Int16[] Data16 = new Int16[Data.Length];
            for (long i = 0; i < Data.Length; ++i)
            {
                long d = (long)(NormalizeRate * Data[i]);

                if (d > Int16.MaxValue)
                {
                    d = Int16.MaxValue;
                    result = false;
                }
                else if (d < Int16.MinValue)
                {
                    d = Int16.MinValue;
                    result = false;
                }

                Data16[i] = (Int16)d;
            }

            result &= WriteWav16(ref Data16, ref Data16, Filename, SamplingRate);

            return result;
        }

        // stereo
        public static bool WriteWav16(ref Int16[] DataLeft, ref Int16[] DataRight, string Filename, long SamplingRate)
        {
            WavHeader WavHeader;

            const int SizeOfSample = 2;
            const int SizeOfChannel = 2;

            long dataLength = DataLeft.Length;
            if (dataLength < DataRight.Length)
                dataLength = DataRight.Length;

            //if (dataLength % 2 == 1)
            //    ++dataLength;

            WavHeader.SizeOfData = (UInt32)(SizeOfSample * SizeOfChannel * dataLength);
            WavHeader.Size = WavHeader.SizeOfData + 44 - 8;

            WavHeader.NumOfChannel = SizeOfChannel;

            WavHeader.SamplingRate = (UInt32)SamplingRate;
            WavHeader.DataSpeed = (UInt32)(SamplingRate * SizeOfSample * SizeOfChannel);

            WavHeader.BlockSize = SizeOfSample * SizeOfChannel;
            WavHeader.BitOfSample = SizeOfSample * 8;

            using (BinaryWriter binWriter = new BinaryWriter(File.OpenWrite(Filename)))
            {
                binWriter.Write(WavHeader.RIFF1);
                binWriter.Write(WavHeader.RIFF2);
                binWriter.Write(WavHeader.RIFF3);
                binWriter.Write(WavHeader.RIFF4);
                binWriter.Write(WavHeader.Size);
                binWriter.Write(WavHeader.WAVEfmt1);
                binWriter.Write(WavHeader.WAVEfmt2);
                binWriter.Write(WavHeader.WAVEfmt3);
                binWriter.Write(WavHeader.WAVEfmt4);
                binWriter.Write(WavHeader.WAVEfmt5);
                binWriter.Write(WavHeader.WAVEfmt6);
                binWriter.Write(WavHeader.WAVEfmt7);
                binWriter.Write(WavHeader.WAVEfmt8);
                binWriter.Write(WavHeader.SizeOfFmtChunk);
                binWriter.Write(WavHeader.FormatID);
                binWriter.Write(WavHeader.NumOfChannel);
                binWriter.Write(WavHeader.SamplingRate);
                binWriter.Write(WavHeader.DataSpeed);
                binWriter.Write(WavHeader.BlockSize);
                binWriter.Write(WavHeader.BitOfSample);
                binWriter.Write(WavHeader.data1);
                binWriter.Write(WavHeader.data2);
                binWriter.Write(WavHeader.data3);
                binWriter.Write(WavHeader.data4);
                binWriter.Write(WavHeader.SizeOfData);

                for (int i = 0; i < dataLength; i++)
                {
                    if (i < DataLeft.Length)
                        binWriter.Write(DataLeft[i]);
                    else
                        binWriter.Write('\x0');

                    if (i < DataRight.Length)
                        binWriter.Write(DataRight[i]);
                    else
                        binWriter.Write('\x0');
                }
            }
            return true;
        }

        // mono
        public static bool WriteWav16(ref Int16[] Data, string Filename, long SamplingRate)
        {
            WavHeader WavHeader;

            const int SizeOfSample = 2;
            const int SizeOfChannel = 1;

            WavHeader.SizeOfData = (UInt32)(SizeOfSample * SizeOfChannel * Data.Length);
            WavHeader.Size = WavHeader.SizeOfData + 44 - 8;

            WavHeader.NumOfChannel = SizeOfChannel;

            WavHeader.SamplingRate = (UInt32)SamplingRate;
            WavHeader.DataSpeed = (UInt32)(SamplingRate * SizeOfSample * SizeOfChannel);

            WavHeader.BlockSize = SizeOfSample * SizeOfChannel;
            WavHeader.BitOfSample = SizeOfSample * 8;

            using (BinaryWriter binWriter = new BinaryWriter(File.OpenWrite(Filename)))
            {
                binWriter.Write(WavHeader.RIFF1);
                binWriter.Write(WavHeader.RIFF2);
                binWriter.Write(WavHeader.RIFF3);
                binWriter.Write(WavHeader.RIFF4);
                binWriter.Write(WavHeader.Size);
                binWriter.Write(WavHeader.WAVEfmt1);
                binWriter.Write(WavHeader.WAVEfmt2);
                binWriter.Write(WavHeader.WAVEfmt3);
                binWriter.Write(WavHeader.WAVEfmt4);
                binWriter.Write(WavHeader.WAVEfmt5);
                binWriter.Write(WavHeader.WAVEfmt6);
                binWriter.Write(WavHeader.WAVEfmt7);
                binWriter.Write(WavHeader.WAVEfmt8);
                binWriter.Write(WavHeader.SizeOfFmtChunk);
                binWriter.Write(WavHeader.FormatID);
                binWriter.Write(WavHeader.NumOfChannel);
                binWriter.Write(WavHeader.SamplingRate);
                binWriter.Write(WavHeader.DataSpeed);
                binWriter.Write(WavHeader.BlockSize);
                binWriter.Write(WavHeader.BitOfSample);
                binWriter.Write(WavHeader.data1);
                binWriter.Write(WavHeader.data2);
                binWriter.Write(WavHeader.data3);
                binWriter.Write(WavHeader.data4);
                binWriter.Write(WavHeader.SizeOfData);

                for (int i = 0; i < Data.Length; i++)
                {
                    binWriter.Write(Data[i]);
                }
            }
            return true;
        }
    }
}
